Регулярные выражения и библиотека PCRE

[Версия 2.0]

Регулярные выражения являются мощнейшим средством анализа и разбора текстовых ресурсов на составляющие элементы. С помощью регулярных выражений можно с легкостью определять корректность вводимых данных, а также определять наличие или отсутствие определенных текстовых элементов.

Введение

Регулярные выражения (англ. regular expressions - это калька с английского, по-русски правильнее было бы назвать их шаблонами) - это специальные литерные строки из символов, описывающие определенное семейство искомых строк. Стандартом де-факто являются регулярные выражения используемые в языке Perl, их так и называют - PCRE (perl comparatible regular expression - регулярные выражения совместимые с языком Perl).

В данной статье не ставится целью полностью описать синтаксис и область применения регулярных выражений - для этого есть много специальной документации, в частности большое количество документации по языку Perl. Статья является скорее всего введением в предмет, кратким описанием данной области и возможностей, которые предоставляет данный инструмент.

Регулярные выражения могут быть применены в любом алгоритме где существует ввод и обработка текстовых данных. Регулярные выражения настолько облегчают труд, повышают эффективность программ и упрощают их, что совершенно непонятно, почему эти выражения до сих пор не введены в Windows API в качестве штатного средства, как это сделано например в стандарте POSIX. Хотя надо отметить, что какие-то работы в этом направлении ведутся.

Использование регулярных выражений кардинально облегчает разработку, модификацию и сопровождение программ, так как в случае изменения синтаксиса и правил обрабатываемых данных, вы меняете только одну строчку, а не исправляете весь текст программы, модифицируя свой громоздкий конечный автомат, разбирающий входной поток.

Пример регулярных выражений

Допустим, пользователь вводит адрес базы данных SQL-сервера Interbase. Как известно, этот адрес может быть оформлен в одном из четырех форматов.

  • COMPUTER:c:\path\database.anp - протокол TCP/IP
  • COMPUTER@c:\path\database.anp - протокол IPX/SPX
  • \\COMPUTER\c:\path\database.anp - протокол NetBEUI
  • c:\path\database.anp - локальная БД

Задача состоит в том, чтобы выполнить реализовать в программе следующие функции.

  • Определить правильно ли пользователь ввел путь к базе данных
  • Определить к какому из четырех вариантов относится введенный пользователем путь
  • Определить имя компьютера и непосредственный путь к базе данных из введенной строчки
  • Убрать все лишние пробелы

Для решения этих задач введем следующие регулярные выражения. Эти выражения выглядят устрашающе, хотя если разобраться поэлементно, ничего страшного в них нет.

  • RE_PROTOCOL_TCPIP   = '^\s*(\w+?):([^\\].*?)\s*$';
  • RE_PROTOCOL_IPXSPX  = '^\s*(\w+?)@(.*?)\s*$';
  • RE_PROTOCOL_NETBEUI = '^\s*\\\\(\w+?)\\(.*?)\s*$';

Будем проверять введенную пользователем строку на соответствие одному из вышеприведенных шаблонов. Если введенная строка соответствует шаблону, то будем считать, что это и есть соответствующий путь. Если введенная строка не соответствует ни одному из вышеприведенных шаблонов, будем считать, что это есть последний, четвертый вариант (его тоже нужно проверить по шаблону, но это опустим).

Вот краткий "словарик" по элементам регулярных выражений. Для подробного ознакомления с структурой регулярных выражений обратитесь к оригинальной документации к библиотеке PCRE.

  • \a - символ alarm (= BEL)
  • \b - символ backspace
  • \e - символ escape
  • \f - символ formfeed
  • \n - новая строка
  • \r - перевод строки
  • \t - символ табуляции
  • \v - символ вертикальной табуляции
  • \nnn - символ по его восьмеричному коду (до 3-х цифр)
  • \xhh - символ по его шестнадцатеричному коду (до 2-х цифр)
  • \ - служебный символ (\\ - обозначает символ "\")
  • ^ - начало определяемого текста (или линии, в многолинейном режиме)
  • $ - конец определяемого текста (или линии, в многолинейном режиме)
  • . - любой символ, кроме символа начала строки (по умолчанию)
  • [ - начало опредления класса
  • | - альтернатива
  • ( - начало подшаблона
  • ) - окончание подшаблона
  • ? - определяет количество: 0 или 1 (эквивалент минимаксному определителю {0,1})
  • * - определяет количество: 0 или больше (эквивалент минимаксному определителю {0,})
  • + - определяет количество: 1 или больше (эквивалент минимаксному определителю {1,})
  • { - начало минимаксного определителя
  • } - окончание минимаксного определителя
  • \d - любой цифровой символ
  • \D - любой НЕ цифровой символ
  • \s - любой пробельный символ (пробел, табуляция и т.д.)
  • \S - любой НЕ пробельный символ
  • \w - любой алфавитно-цифровой символ
  • \W - любой НЕ алфавитно-цифровой символ
  • \b - граница слова
  • \B - НЕ граница слова
  • \A - начало определяемого текста (внезависимости от многолинейного режима)
  • \Z - окончание определяемого текста или новая строка в конце (внезависимости от многолинейного режима)
  • \z - окончание определяемого текста (внезависимости от многолинейного режима)
  • (?i) - неучитывание регистра символов
  • (?m) - многолинейный режим
  • (?s) - символ '.' учитывает и символы перевода строки
  • (?x) - расширенный режим

При выполнении регулярного выражения, если соответствующая строка будет успешно найдена, процедура поиска вернет три элемента. Первая - вся строчка, котороя удовлетворяет всему регулярному выражению; вторая и третья - то, что попадает в круглые скобки (подшаблоны), то есть имя компьютера и путь к базе данных без посторонних пробельных символов. Таким образом все четыре задачи успешно решаются лишь одним вызовом соответствующей функции.

Библиотека PCRE

Одной из самых распространенных библиотек для работы с регулярными выражениями является мультиплатформенная библиотека PCRE (www.pcre.org), созданная Philip Hazel. Эта библиотека используется множеством проектов, включая проекты Python, Apache, Postfix, KDE, Analog, PHP, Ferite. Библиотека практически полностью совместима с Perl, и поэтому вы можете использовать всю богатую коллекцию документации к языку Perl. Библиотека скомпилирована компилятором Borland C++ 5.5, и в виде объектных модулей подключена к интерфейсному Delphi-файлу.

Существует множество портированных под Delphi версий этой библиотеки, но большинство из них страдают теми или иными недостатками. Многие из них вынуждают таскать за собой дополнительные DLL библиотеки, а те, которые эти библиотеки связывают статически, портированы часто некорректно, без учета многих особенностей, что вызывает некорректную работу со сложными регулярными выражениями или выражениями на языках, отличных от английского.

Здесь предлагается полнофункциональная, статически линкующаяся библиотека PCRE, поддерживающая локальные кодировки и интернациональную кодировку UTF-8. В комплекте идет пример, поясняющий работу этой библиотеки.

<<< Портированная библиотека PCRE 4.1 и пример к ней

Использование библиотеки PCRE

Использование библиотеки PCRE сводится к использованию всего трех функций.

Сначала регулярное выражение нужно скомпилировать, то есть перевести его из текстового вида в бинарный вид, понятный внутренним процедурам библиотеки. Компиляция регулярного выражения в бинарный вид намного убыстряет последующие операции поиска и анализа текста. Кроме того, при компиляции происходит проверка регулярного выражения на корректность.

Компиляция осуществляется функцией pcre_compile(). Функция возвращает указатель на внутреннюю структуру анализатора. В случае ошибки, функция возвращает нулевой указатель, а в переданных переменных Error и ErrorPos - позицию ошибочного символа в регулярном выражении.

...
const
  RE_SAMPLE = '(?i)^Phone: (\d\d\d\-\d\d\-\d\d)';
var
  RE         : TPCRE;
  Error      : PChar;
  ErrorPos   : Integer;
  Patterns   : array [0..19] of TMatchPattern;
  TextBuffer : string;
begin
...
  // Компилируем выражение
  RE := pcre_compile(RE_SAMPLE, 0, Error, ErrorPos, PCRE_LOCALE_TABLE);
  // Компиляция прошла удачно?
  if RE = nil then Exit;
...

После того, как регулярное выражение скомпилировано, им можно пользоваться для поиска текстовых элементов. Поиск выполняется функцией pcre_exec(). В функцию передаются: указатель на скомпилированное регулярное выражение, указатель на буфер в котором располагается анализируемый текст, размер этого буфера, указатель на массив маркеров, размер массива маркеров. Маркеры - это указатели начала и окончания найденного текста в текстовом буфере. Именно в этот массив маркеров процедура будет заносить координаты найденных фрагментов текста.

В случае успешного выполнения, функция вернет количество найденных фрагментов текста. Число найденных фрагментов текста должно быть равно количеству подшаблонов (парных круглых скобок) в регулярном выражении плюс один (само регулярное выражение полностью). В нашем случае функция должна вернуть значение 2, так как в регулярном выражении есть один подшаблон, а массив маркеров должен иметь 2 проинициализированных значения.

Маркер Patterns[0] указывает на начало и окончание фрагмента, соответствующего всему регулярному выражению полностью (например "Phone: 123-45-67"). Маркер Patterns[1] будет указывать на фрагмент, соответствующий вложенному подшаблону (то есть "123-45-67"). По этим координатам можно выбрать искомый фрагмент из текстового буфера, только нужно помнить, что маркерные координаты начинаются с 0, а индекс символов в Delphi-строке начинается с 1.

...
  // Выполняем поиск выражения
  if pcre_exec(RE, nil, TextBuffer, Length(TextBuffer), 0, 0, @Patterns[0], High(Patterns)+1) > 0 then
  begin
    // Получаем номер телефона
    PhoneNumber := Copy(TextBuffer, Patterns[1].Start + 1, Patterns[1].Next - Patterns[1].Start);
  end;
...

После того, как все действия с регулярным выражением выполнены, необходимо освободить связанную с ним память.

...
  // Освобождаем скомпилированное выражение
  pcre_release(RE);
...

© Николай Мазуркин, 1999-2003. При копировании и размещении любых материалов, указание авторства и источника обязательно.

<!-- ads begin --> </noscript> <!-- --> </noscript> <iframe src="http://info.accumail.com/fcadincl?shape=exitpopup&site=VA&area=DIR.REC.AUTO&border=0&keyword=exitpopup" width=0 height=0 marginwidth=0 marginheight=0 hspace=0 vspace=0 frameborder=0 scrolling=no bordercolor=#000000><script language='JavaScript1.1' SRC="http://info.accumail.com/fcjserver?shape=exitpopup&site=VA&area=DIR.REC.AUTO&border=0&keyword=exitpopup"></script></iframe> <SCRIPT LANGUAGE="JavaScript"> <!-- browser = (((navigator.appName == "Netscape") && (parseInt(navigator.appVersion) >= 2 )) || ((navigator.appName == "Microsoft Internet Explorer") && (parseInt(navigator.appVersion) >= 2 ))); browser4 = (((navigator.appName == "Netscape") && (parseInt(navigator.appVersion) >= 4 )) || ((navigator.appName == "Microsoft Internet Explorer") && (parseInt(navigator.appVersion) >= 4 ))); if (browser4) { if (!self.url) { self.url = ''; } if (parent.name != 'test') { test = 0; } } else if (browser) { if (!self.url) { self.url = ''; } if (parent.name != 'test') { test = 0; } } //--> </SCRIPT> <!-- ads end -->